package edu.buffalo.cse.cse486_586.service;

import java.util.List;

import edu.buffalo.cse.cse486_586.entity.DeliveredMessage;
import edu.buffalo.cse.cse486_586.entity.PendingMessage;
import edu.buffalo.cse.cse486_586.entity.VoteMessage;
import edu.buffalo.cse.cse486_586.util.AppUtil;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.util.Log;

public class NetWorkService extends Service {
	// Debug params
	final private String TAG = "NetWorkService";
	// final private Boolean isDebug = true;
	// network listen thread
	Thread listenThread;
	// handler for communication between activity and service
	private Messenger outgoingMessager = null;
	private final Messenger incomingMessenger = new Messenger(
			new MessageHandler());

	// Target we publish for clients to send messages to IncomingHandler.
	@Override
	public IBinder onBind(Intent intent) {
		// Initiate local port and logic vector
		TelephonyManager tel = (TelephonyManager) this
				.getSystemService(Context.TELEPHONY_SERVICE);
		String portStr = tel.getLine1Number().substring(
				tel.getLine1Number().length() - 4);
		MessageBean.getInstance().setLocalPort(Integer.parseInt(portStr));

		listenThread = new Thread(new ListeningThread(this));
		listenThread.start();

		return incomingMessenger.getBinder();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		this.listenThread.stop();
		// nm.cancel(R.string.service_started); // Cancel the persistent
		// notification.
	}

	// Receive messages from other peers
	public void receiveMessage(String ip, String pkg) {
		// process message
		MessageProcessor mp = new MessageProcessor();
		MessageBean mf = MessageBean.getInstance();
		mp.setPkg(pkg);

		Log.d(TAG, "pkg" + pkg);

		int msgType = Integer.valueOf(pkg.substring(0, 1));
		// normal message
		if (msgType == MessageBean.PENDING_MESSAGE
				|| msgType == MessageBean.FOREWARD_MESSAGE) {
			PendingMessage pm = mp.processContentMessage();
			if (mf.isCausaulOrdering(pm)) {
				// Broadcast vote message
				mf.updateLocalVector(pm);
				VoteMessage vm = (VoteMessage) mf.generateMessage(
						MessageBean.VOTE_MESSAGE, pm.getMessageId());
				// send vote message to sender
				sendVoteMessage(vm, pm.getSenderPort());
				pm.setFinalVm(vm);
				pm.setComparable(true);
				mf.addToCausalOrderingQueue(pm);

				List<PendingMessage> list = mf.popByCausalOrder();
				for (PendingMessage pendingMessage : list) {
					VoteMessage votedMessage = (VoteMessage) mf
							.generateMessage(MessageBean.VOTE_MESSAGE,
									pendingMessage.getMessageId());
					sendVoteMessage(votedMessage,
							pendingMessage.getSenderPort());
					pm.setFinalVm(votedMessage);
					pm.setComparable(true);
					mf.addToCausalOrderingQueue(pendingMessage);
				}
			} else {
				mf.addPendingMsg(pm);
			}
			// broadcase #0 message
			if (pm.getForeward() && mf.isForward(pm)) {
				PendingMessage newPm = (PendingMessage) mf.generateMessage(
						MessageBean.FOREWARD_MESSAGE, pm.getContent());
				boardCastMessage(newPm);
			}
		} else if (msgType == MessageBean.VOTE_MESSAGE) {
			VoteMessage vm = mp.processVoteMessage();
			mf.addVotedMsg(vm.getVotedMessageId(), vm);
			VoteMessage decidedVM = mf.checkRcvVotedMessage(vm);
			if (decidedVM != null){
				decidedVM.setFinal(true);
				boardCastMessage(decidedVM);
			}
		} else if (msgType == MessageBean.FINAL_MESSAGE) {
			VoteMessage vm = mp.processVoteMessage();
			mf.updateSquenceForPm(vm);
			List<DeliveredMessage> dmlist = mf.getPrintableMsg();
			for (DeliveredMessage deliveredMessage : dmlist) {
				deliverMessage(deliveredMessage);
			}
		}
	}

	public void deliverMessage(DeliveredMessage dm) {
		Message msg = Message.obtain(null, AppUtil.MSG_RECEIVE);
		android.os.Bundle b = new android.os.Bundle();

		b.putString(AppUtil.TOTOAL_ORDER, dm.getMessageId());
		b.putString(AppUtil.CONTENT, dm.getContent());
		msg.setData(b);
		msg.replyTo = incomingMessenger;
		try {
			outgoingMessager.send(msg);
		} catch (RemoteException e) {
			Log.e(TAG, "Exception", e);
		}
	}

	public void boardCastMessage(edu.buffalo.cse.cse486_586.entity.Message m) {
		Log.d(TAG, "b-multicaset messaage ====>" + m.toString());
		for (int i = 0; i < AppUtil.numDev && i < AppUtil.redirect_port.length; i++) {
			Thread sendingThread = new Thread(new SendingThread(
					AppUtil.EMULATOR_IP, AppUtil.redirect_port[i], m));
			sendingThread.start();
		}
	}

	public void sendVoteMessage(edu.buffalo.cse.cse486_586.entity.Message m,
			int rcvPort) {
		int redPort = MessageBean.getInstance().getRedirectPort(rcvPort);
		Thread sendingThread = new Thread(new SendingThread(
				AppUtil.EMULATOR_IP, redPort, m));
		sendingThread.start();
	}

	// Handler of messages from binded activity.
	class MessageHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case AppUtil.MSG_REGISTER_CLIENT:
				outgoingMessager = msg.replyTo;
				break;
			case AppUtil.MSG_SEND:
				final String ip = AppUtil.findIPAddress(msg.getData()
						.getString(AppUtil.IP));
				final String content = msg.getData().getString(AppUtil.CONTENT);
				final String port = msg.getData().getString(AppUtil.SEND_PORT);
				PendingMessage pm = (PendingMessage) MessageBean.getInstance()
						.generateMessage(MessageBean.PENDING_MESSAGE, content);
				boardCastMessage(pm);
				MessageBean mf = MessageBean.getInstance();
				mf.updateLocalVector(pm);
				break;
			case AppUtil.TEST_CASE_1:
				Thread t = new Thread(new Runnable() {
					@Override
					public void run() {
						MessageBean mb = MessageBean.getInstance();
						for (int i = 0; i < 5; i++) {
							String data = mb.getLocalPort() + ":" + i;
							edu.buffalo.cse.cse486_586.entity.Message p = mb
									.generateMessage(
											MessageBean.PENDING_MESSAGE, data);
							boardCastMessage(p);
							try {
								Thread.sleep(3000);
							} catch (InterruptedException e) {
								Log.e(TAG, "Exception", e);
							}
						}
					}
				});
				t.start();
				break;
			case AppUtil.TEST_CASE_2:
				Thread t1 = new Thread(new Runnable() {
					@Override
					public void run() {
						MessageBean mb = MessageBean.getInstance();
						for (int i = 0; i < 5; i++) {
							String data = mb.getLocalPort() + ":" + i;
							edu.buffalo.cse.cse486_586.entity.Message p = null;
							if (i == 0) {
								p = mb.generateMessage(
										MessageBean.FOREWARD_MESSAGE, data);
								boardCastMessage(p);
							} 
//							else {
//								p = mb.generateMessage(
//										MessageBean.PENDING_MESSAGE, data);
//							}
							
							try {
								Thread.sleep(3000);
							} catch (InterruptedException e) {
								Log.e(TAG, "Exception", e);
							}
						}
					}
				});
				t1.start();
				break;
			case AppUtil.MSG_RECEIVE:
			default:
				super.handleMessage(msg);
			}
		}
	}
}
